﻿using System.Linq;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
using Scrabble.Controls;

namespace Scrabble.Behaviors
{
    public class DragDropBehavior : Behavior<FrameworkElement>
    {
        #region IsDragSourceProperty

        public static readonly DependencyProperty IsDragSourceProperty = DependencyProperty.RegisterAttached("IsDragSource", 
            typeof (bool), typeof (DragDropBehavior),
            new FrameworkPropertyMetadata(false, OnIsDragSourceChanged));

        public static bool GetIsDragSource(DependencyObject sender)
        {
            return (bool) sender.GetValue(IsDragSourceProperty);
        }

        public static void SetIsDragSource(DependencyObject sender, bool newValue)
        {
            sender.SetValue(IsDragSourceProperty, newValue);
        }
        
        #endregion

        static void OnIsDragSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            var behaviors = Interaction.GetBehaviors(sender);

            if ((bool)args.NewValue)
            {
                behaviors.Add(new DragDropBehavior());
            }
            else 
            {
                var existingBehavior = behaviors.OfType<DragDropBehavior>().FirstOrDefault();
                if (existingBehavior != null) behaviors.Remove(existingBehavior);
            }
        }

        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.PreviewMouseLeftButtonDown += OnPreviewMouseLeftButtonDown;
        }

        protected override void OnDetaching()
        {
            AssociatedObject.PreviewMouseLeftButtonDown -= OnPreviewMouseLeftButtonDown;
            base.OnDetaching();
        }

        private void OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            new DragSource(AssociatedObject);
        }

        private class DragSource : IDragSource
        {
            private readonly FrameworkElement _source;
            private readonly Rect _startingBounds;

            public DragSource(FrameworkElement source)
            {
                _source = source;

                UpdatePosition();

                _startingBounds = new Rect(Position.X, Position.Y, SystemParameters.MinimumHorizontalDragDistance, SystemParameters.MinimumVerticalDragDistance);
                _startingBounds.Offset(-_startingBounds.Width / 2, -_startingBounds.Height / 2);

                _source.MouseMove += OnMouseMove;
                _source.MouseLeftButtonUp += OnMouseLeftButtonUp;
            }

            public object Data
            {
                get { return _source.DataContext; }
            }

            public Size Size
            {
                get { return _source.RenderSize; }
            }

            public Point DragOffset
            {
                get { throw new System.NotImplementedException(); }
            }

            public bool IsAdornerVisible
            {
                get
                {
                    return Visual != null && Visual.Visibility == Visibility.Visible;
                }
                set
                {
                    if (Visual == null || value == IsAdornerVisible) return;

                    if (value) UpdatePosition();

                    Visual.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
                }
            }

            public void UpdatePosition()
            {
                var cursorPosition = Native.CursorPositionOnScreen();

                if (Visual != null)
                    Visual.Offset += cursorPosition - Position;

                Position = cursorPosition;
            }

            void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
            {
                Unsubscribe();
            }

            private void OnMouseMove(object sender, MouseEventArgs e)
            {
                UpdatePosition();

                if (_startingBounds.Contains(Position)) return;

                Unsubscribe();

                var adornerLayer = AdornerLayer.GetAdornerLayer(_source);
                if (adornerLayer != null)
                {
                    Visual = new DragAdorner(_source, Size, new VisualBrush(_source)) { Visibility = Visibility.Collapsed, Opacity = 0.8 };
                    adornerLayer.Add(Visual);
                }

                DragDrop.DoDragDrop(_source, new DataObject(typeof(IDragSource), this), DragDropEffects.Move);

                if (adornerLayer == null) return;

                AdornerLayer.GetAdornerLayer(_source).Remove(Visual);
                Visual = null;
            }

            private Point Position
            {
                get;
                set;
            }

            private DragAdorner Visual
            {
                get;
                set;
            }

            private void Unsubscribe()
            {
                _source.MouseLeftButtonUp -= OnMouseLeftButtonUp;
                _source.MouseMove -= OnMouseMove;
            }
        }
    }
}
